//-------------------------------------------------------------------
//	Copyright (c) 2013-2014  Zhirnov Andrey
//	This file is part of the "UX OpwnGL Framework" project.
//	See copyright notice in "UX_GL_Framework.h".
//-------------------------------------------------------------------

#pragma once

#include "VoxelFile.h"

namespace Voxels
{
	/*
	example:

	template <typename T>
	struct VoxelTerrain_Compute
	{
		void operator () (const T &inVoxel, T &outVoxel, const uvec3 &coord)		{}	// per pixel
		void operator () (const VoxelsBlockPtr &inBlock, OUT VoxelsBlock &outBlock) {}	// per block

		void Begin ()		{}
		void End ()			{}

		bool BeginBlock (const ivec3 &pos)		{ return true; }	// return false to skip this block
		void EndBlock ()						{}
		bool OnError (EProcessingError::type)	{ return true; }	// return true to continue

		bool NeedToLoadBlock ()		{ return true; }
		bool NeedToStoreBlock ()	{ return true; }
	};
	*/

	//
	// Voxel Terrain
	//

	template < typename TVoxel,
			   uint VoxelID
			 >
	class VoxelTerrain
	{
	// types
	public:
		typedef TVoxel								voxel_t;
		typedef VoxelTerrain< TVoxel, VoxelID >		Self;

		enum {
			VOXEL_ID		= VoxelID,
			BYTE_PER_VOXEL	= sizeof(voxel_t),
			MIN_BLOCK_SIZE	= 32,
		};

		struct EProcessingError
		{
			enum type
			{
				NONE	= 0,
				FAIL_TO_LOAD,
				FAIL_TO_STORE,
			};
		};

	private:
		template <typename T>
		struct ComputeWrapper;


	// variables
	private:
		VoxelFile		_file;
		
		VoxelsBlockPtr	_outBlock;


	// methods
	public:
		VoxelTerrain ();
		~VoxelTerrain ();

		bool Open (uni_c_string filePath);
		bool Create (uni_c_string filePath, const uvec3 &blockSize, const uvec3 &numBlocks);
		void Close ();
		
		bool IsExist (uni_c_string filePath) const;

		bool SetVoxelInBorder (const voxel_t &positiveZVoxel, const voxel_t &negativeZVoxel, const voxel_t &xyVoxel);

		template <typename T>
		bool ProcessBlocks (T &compute);
		
		template <typename T>
		bool ProcessBlockPixels (T &compute);
		
		uvec3			Dimension ()			const	{ return _file.Dimension(); }
		uvec3 const &	NumBlocks ()			const	{ return _file.NumBlocks(); }
		uvec3 const &	BlockSize ()			const	{ return _file.BlockSize(); }
		uint			PixelPerVoxel ()		const	{ return _file.GetBlockWithBorderInfo().pixelPerVoxel; }
		uint			TextureXFramesPOT ()	const	{ return _file.GetBlockWithBorderInfo().xLayersPOT; }
		uint			BorderSize ()			const	{ return _file.GetBlockWithBorderInfo().border; }
		const char *	TexDataTypeName ()		const;
		static uint		VoxelFormatId ()				{ return VOXEL_ID; }
	};

	
/*
=================================================
	constructor
=================================================
*/
	template <typename V, uint ID>
	inline VoxelTerrain<V, ID>::VoxelTerrain ()
	{}
	
/*
=================================================
	destructor
=================================================
*/
	template <typename V, uint ID>
	inline VoxelTerrain<V, ID>::~VoxelTerrain ()
	{}
	
/*
=================================================
	Open
=================================================
*/
	template <typename V, uint ID>
	inline bool VoxelTerrain<V, ID>::Open (uni_c_string filePath)
	{
		CHECK_RETURN( _file.Open( filePath ) );

		_outBlock = new VoxelsBlock( _file.GetBlockWithBorderInfo() );

		CHECK_RETURN( _outBlock->Create() );

		return true;
	}

/*
=================================================
	Create
=================================================
*/
	template <typename V, uint ID>
	inline bool VoxelTerrain<V, ID>::Create (uni_c_string filePath, const uvec3 &blockSize, const uvec3 &numBlocks)
	{
		CHECK_RETURN( _file.Create( filePath, blockSize, numBlocks, BYTE_PER_VOXEL, VoxelFormatId() ) );
		
		_outBlock = new VoxelsBlock( _file.GetBlockWithBorderInfo() );
		
		CHECK_RETURN( _outBlock->Create() );

		return true;
	}

/*
=================================================
	IsExist
=================================================
*/
	template <typename V, uint ID>
	inline bool VoxelTerrain<V, ID>::IsExist (uni_c_string filePath) const
	{
		return _file.IsExist( filePath );
	}

/*
=================================================
	Create
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrain<V, ID>::Close ()
	{
		_file.Close();
	}
	
/*
=================================================
	SetVoxelInBorder
=================================================
*/
	template <typename V, uint ID>
	inline bool VoxelTerrain<V, ID>::SetVoxelInBorder (const voxel_t &positiveZVoxel, const voxel_t &negativeZVoxel,
														const voxel_t &xyVoxel)
	{
		return _file.VoxelsInBorder().Create( positiveZVoxel, negativeZVoxel, xyVoxel );
	}

/*
=================================================
	TexDataTypeName
=================================================
*/
	template <typename V, uint ID>
	inline const char * VoxelTerrain<V, ID>::TexDataTypeName () const
	{
		const cl_img_channel_order::type	order = _file.GetBlockWithBorderInfo().clFormat.eOrder;

		switch ( order )
		{
			case cl_img_channel_order::RGBA :	return "int4";
			case cl_img_channel_order::RG :		return "int2";
			case cl_img_channel_order::R :		return "int";
		}
		WARNING( "invalid image channel order" );
		return "";
	}

/*
=================================================
	ProcessBlocks
----
	Process all terrain as separated blocks
=================================================
*/
	template <typename V, uint ID>
	template <typename T>
	inline bool  VoxelTerrain<V, ID>::ProcessBlocks (T &compute)
	{
		CHECK_VALUE( _file.IsOpened() );

		_file.EnableBorderCache( false );
		compute.Begin();

		const uvec3	count = _file.NumBlocks();

		for (uint z = 0; z < count.z; ++z)
		{
			for (uint y = 0; y < count.y; ++y)
			{
				for (uint x = 0; x < count.x; ++x)
				{
					VoxelsBlockPtr	in_block;

					const ivec3	pos = ivec3(x, y, z);
					
					if ( not compute.BeginBlock( pos * _file.BlockSize().To<ivec3>() ) )
						continue;

					if ( compute.NeedToLoadBlock() )
					{
						if ( not _file.Load( pos, in_block ) )
						{
							if ( not compute.OnError( EProcessingError::FAIL_TO_LOAD ) ) {
								RETURN_ERROR( "can't load block file" );
							} else
								continue;
						}
					}

					compute( in_block, *_outBlock );

					if ( compute.NeedToStoreBlock() )
					{
						if ( not _file.Store( pos, *_outBlock ) )
						{
							if ( not compute.OnError( EProcessingError::FAIL_TO_STORE ) ) {
								RETURN_ERROR( "can't store block to file" );
							} else
								continue;
						}
					}

					compute.EndBlock();
				}
			}
		}

		compute.End();
		_file.EnableBorderCache( true );

		CHECK_RETURN( _file.Flush() );

		return true;
	}
	
/*
=================================================
	ProcessBlockPixels
----
	Process all terrain as separated blocks and
	call compute() for each pixel in block.
=================================================
*/
	template <typename V, uint ID>
	template <typename T>
	inline bool VoxelTerrain<V, ID>::ProcessBlockPixels (T &compute)
	{
		ComputeWrapper<T> compute_wrapper( compute );

		return ProcessBlocks( compute_wrapper );
	}
	
/*
=================================================
	ComputeWrapper
=================================================
*/
	template <typename V, uint ID>
	template <typename T>
	struct VoxelTerrain<V, ID>::ComputeWrapper
	{
	private:
		T &		_compute;
		uvec3	_currentBlockCoord;

	public:
		ComputeWrapper (T &compute) : _compute(compute)
		{}

		void operator () (const VoxelsBlock &inBlock, OUT VoxelsBlock &outBlock)
		{
			CHECK_ARGUMENT_R( All( inBlock.Dimension() == outBlock.Dimension() ), );

			const uvec3 &	block_size = inBlock.Dimension();

			for (uint z = 0; z < block_size.z; ++z)
			{
				for (uint y = 0; y < block_size.y; ++y)
				{
					for (uint x = 0; x < block_size.x; ++x)
					{
						uvec3 const		coord		= uvec3(x, y, z);
						voxel_t const &	in_voxel	= inBlock.GetVoxel( coord );
						voxel_t	&		out_voxel	= outBlock.GetVoxel( coord );

						_compute( in_voxel, out_voxel, coord + _currentBlockCoord );
					}
				}
			}
		}

		void Begin ()						{ return _compute.Begin(); }
		void End ()							{ return _compute.End(); }
		
		bool BeginBlock (const uvec3 &pos)	{ _currentBlockCoord = pos;  return _compute.BeginBlock( pos ); }
		void EndBlock ()					{ return _compute.EndBlock(); }

		bool NeedToLoadBlock ()				{ return _compute.NeedToLoadBlock(); }
		bool NeedToStoreBlock ()			{ return _compute.NeedToStoreBlock(); }
	};


}	// Voxels